{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Automatic Values"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Enumerations have a builtin mechanism to auto assign values to members."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is often useful when you migth have a simple associated integer value that is sequential, for example `1, 2, 3, 4, ...`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can easily let enums assign their own values this way, using the `auto()` function in the enum module.\n",
    "\n",
    "By default it will use sequential integers, starting at `1`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import enum"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class State(enum.Enum):\n",
    "    WAITING = enum.auto()\n",
    "    STARTED = enum.auto()\n",
    "    FINISHED = enum.auto()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WAITING 1\n",
      "STARTED 2\n",
      "FINISHED 3\n"
     ]
    }
   ],
   "source": [
    "for member in State:\n",
    "    print(member.name, member.value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can actually mix in our own values too, but we have to be really careful - nothing in the Python documentation states what will/will not work - their only advice is ```Care must be taken if you mix auto with other values```. That's not saying much, and so I **never** mix auto-generated values and my own - just to be on the safe side."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This seems to work fine:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class State(enum.Enum):\n",
    "    WAITING = 5\n",
    "    STARTED = enum.auto()\n",
    "    FINISHED = enum.auto()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WAITING 5\n",
      "STARTED 6\n",
      "FINISHED 7\n"
     ]
    }
   ],
   "source": [
    "for member in State:\n",
    "    print(member.name, member.value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But observe what happens here:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WAITING 1\n",
      "FINISHED 2\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "mappingproxy({'WAITING': <State.WAITING: 1>,\n",
       "              'STARTED': <State.WAITING: 1>,\n",
       "              'FINISHED': <State.FINISHED: 2>})"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "class State(enum.Enum):\n",
    "    WAITING = enum.auto()\n",
    "    STARTED = 1\n",
    "    FINISHED = enum.auto()\n",
    "    \n",
    "for member in State:\n",
    "    print(member.name, member.value)\n",
    "    \n",
    "State.__members__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, `STARTED` ended up being an alias for `WAITING` - not what my intention was.\n",
    "\n",
    "Using `@unique` does not solve the issue, although it does make it immediately clear that there is a problem:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "duplicate values found in <enum 'State'>: STARTED -> WAITING\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    @enum.unique\n",
    "    class State(enum.Enum):\n",
    "        WAITING = enum.auto()\n",
    "        STARTED = 1\n",
    "        FINISHED = enum.auto()\n",
    "except ValueError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Enum classes use the `_generate_next_value_` method to generate these automatic values, and we can actually override this to provide our implementation of an automatic value. The default implemtation currently generates a sequence of numbers, but the actual algorithm is an implementation detail - i.e. we cannot rely on any specific sequence of values being generated.\n",
    "\n",
    "We can however override it if we wish:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a 1 0 []\n",
      "b 1 1 [100]\n",
      "c 1 2 [100, 100]\n"
     ]
    }
   ],
   "source": [
    "class State(enum.Enum):\n",
    "    def _generate_next_value_(name, start, count, last_values):\n",
    "        print(name, start, count, last_values)\n",
    "        return 100\n",
    "    \n",
    "    a = enum.auto()\n",
    "    b = enum.auto()\n",
    "    c = enum.auto()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see the `last_values` property is a list of all the preceding values used for member. The `count` property is simply the number of enum members already created (including aliases!). The `name` property is the name of the member. The `start` argument is actually only used when we create enumerations using a functional approach (very similar to how we created named tuples) - but I am not going to cover this in this course (feel free to explore the Python docs, it's quite straightforward)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's see a more interesting example of how we could use this override. Let's say we want the associated values to be random integers, where we do not want duplicates."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "\n",
    "random.seed(0)\n",
    "\n",
    "class State(enum.Enum):\n",
    "    def _generate_next_value_(name, start, count, last_values):\n",
    "        while True:\n",
    "            new_value = random.randint(1, 100)\n",
    "            if new_value not in last_values:\n",
    "                return new_value\n",
    "            \n",
    "    a = enum.auto()\n",
    "    b = enum.auto()\n",
    "    c = enum.auto()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a 50\n",
      "b 98\n",
      "c 54\n"
     ]
    }
   ],
   "source": [
    "for member in State:\n",
    "    print(member.name, member.value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Another example, shown in the Python docs is using the string of the member name as the value. In this example I choose to title case the name:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WAITING Waiting\n",
      "STARTED Started\n",
      "FINISHED Finished\n"
     ]
    }
   ],
   "source": [
    "class State(enum.Enum):\n",
    "    def _generate_next_value_(name, start, count, last_values):\n",
    "        return name.title()  \n",
    "    \n",
    "    WAITING = enum.auto()\n",
    "    STARTED = enum.auto()\n",
    "    FINISHED = enum.auto()\n",
    "    \n",
    "for member in State:\n",
    "    print(member.name, member.value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we want to make our `_generate_next_value_` implementation reusable across more than one enumeration, we could create an enumeration that only implements this functionality, and then use that as the parent class to our other enumerations:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "class NameAsString(enum.Enum):\n",
    "    def _generate_next_value_(name, start, count, last_values):\n",
    "        return name.lower()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Enum1(NameAsString):\n",
    "    A = enum.auto()\n",
    "    B = enum.auto()\n",
    "    \n",
    "class Enum2(NameAsString):\n",
    "    WAIT = enum.auto()\n",
    "    RUNNING = enum.auto()\n",
    "    FINISHED = enum.auto()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "A a\n",
      "B b\n",
      "WAIT wait\n",
      "RUNNING running\n",
      "FINISHED finished\n"
     ]
    }
   ],
   "source": [
    "for member in Enum1:\n",
    "    print(member.name, member.value)\n",
    "    \n",
    "for member in Enum2:\n",
    "    print(member.name, member.value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Note"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sometimes, we don't actually care about the associated value for each member. In that case we can certainly use `auto()`, but the problem might be that users of our enumeration rely on that associated value."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Later, if we want to add items to the enumeration (somewhere in the middle), our users' code would break."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We might therefore want to discourage our users from ever using the associated value, and only using the keys."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Although we can (and should) document this, we can also enforce this using a simple trick. We assign an instance of `object` as the value for each member. There is very little our users can then do with that value, and so we are ensuring their safety."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "class State(enum.Enum):\n",
    "    WAIT = object()\n",
    "    RUNNING = object()\n",
    "    FINISHED = object()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(<State.WAIT: <object object at 0x7fab7807bd30>>,\n",
       " <State.RUNNING: <object object at 0x7fab7807bd40>>,\n",
       " <State.FINISHED: <object object at 0x7fab7807bda0>>)"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "State.WAIT, State.RUNNING, State.FINISHED"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In order for a user to use the value, they would have to first get a handle to the object instance itself - they would never get that back from a literal string, integer, etc."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, instead of remembering to use `object()` for every member, we could use a base class to make it reusable (and a consistent implementation), and the auto functionality:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ValuelessEnum(enum.Enum):\n",
    "    def _generate_next_value_(name, start, count, last_values):\n",
    "        return object()\n",
    "    \n",
    "class State(ValuelessEnum):\n",
    "    WAIT = enum.auto()\n",
    "    RUNNING = enum.auto()\n",
    "    FINISHED = enum.auto()\n",
    "    \n",
    "class Errors(ValuelessEnum):\n",
    "    NumberError = enum.auto()\n",
    "    IndexError = enum.auto()\n",
    "    TimeoutError = enum.auto()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(<State.WAIT: <object object at 0x7fab7807bdd0>>,\n",
       " <Errors.TimeoutError: <object object at 0x7fab7807be40>>)"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "State.WAIT, Errors.TimeoutError"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By using a base class, we could technically change our implementation of how the values are generated without having to touch our subclassed enumerations:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ValuelessEnum(enum.Enum):\n",
    "    def _generate_next_value_(name, start, count, last_values):\n",
    "        while True:\n",
    "            new_value = random.randint(1, 100)\n",
    "            if new_value not in last_values:\n",
    "                return new_value\n",
    "    \n",
    "class State(ValuelessEnum):\n",
    "    WAIT = enum.auto()\n",
    "    RUNNING = enum.auto()\n",
    "    FINISHED = enum.auto()\n",
    "    \n",
    "class Errors(ValuelessEnum):\n",
    "    NumberError = enum.auto()\n",
    "    IndexError = enum.auto()\n",
    "    TimeoutError = enum.auto()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(<State.WAIT: 6>, <Errors.TimeoutError: 39>)"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "State.WAIT, Errors.TimeoutError"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Auto and Aliases"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "I want to touch back on the `count` argument of `_generate_next_value_` when are are dealing with aliases."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since the default implementation of `_generate_next_value_` generates sequential integer numbers, we can never create aliases using this default."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, nothing stops us from doing so when we have our own implementation of that function. In that case `count` will reflect the number of items created, **including** any aliases."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "count=3\n",
      "count=4\n",
      "count=5\n"
     ]
    }
   ],
   "source": [
    "class Aliased(enum.Enum):\n",
    "    def _generate_next_value_(name, start, count, last_values):\n",
    "        print(f'count={count}')\n",
    "        if count % 2 == 1:\n",
    "            # odd, make this member an alias of the previous one\n",
    "            return last_values[-1]\n",
    "        else:\n",
    "            # make a new value\n",
    "            return last_values[-1] + 1\n",
    "       \n",
    "    GREEN = 1\n",
    "    GREEN_ALIAS = 1\n",
    "    RED = 10\n",
    "    CRIMSON = enum.auto()\n",
    "    BLUE = enum.auto()\n",
    "    AQUA = enum.auto()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see `_generate_next_value_` was called for the last three members of our enum, and reflect the number of items that were created to that point, including aliases."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<Aliased.GREEN: 1>, <Aliased.RED: 10>, <Aliased.BLUE: 11>]"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(Aliased)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'GREEN': <Aliased.GREEN: 1>,\n",
       "              'GREEN_ALIAS': <Aliased.GREEN: 1>,\n",
       "              'RED': <Aliased.RED: 10>,\n",
       "              'CRIMSON': <Aliased.RED: 10>,\n",
       "              'BLUE': <Aliased.BLUE: 11>,\n",
       "              'AQUA': <Aliased.BLUE: 11>})"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Aliased.__members__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
